from math import floor
from random import random


def _exit_from_maze(board):
    moves = {'right': [1, 0], 'left': [-1, 0],
             'up': [0, -1], 'down': [0, 1]}
    y = [i for i, v in enumerate(board) if '*' in v][0]
    x = board[y].index('*')
    mirrors = {
        '/': {'right': 'up', 'left': 'down', 'down': 'left', 'up': 'right'},
        '\\': {'right': 'down', 'left': 'up', 'down': 'right', 'up': 'left'}
    }

    def init_direction():
        if x == 0:
            return 'right'
        elif y == 0:
            return 'down'
        elif y == len(board)-1:
            return 'up'
        else:
            return 'left'
    d = init_direction()
    dist = 0
    while board[y][x] != '#':
        l = board[y][x]
        if l in '/\\':
            d = mirrors[l][d]
        x += moves[d][0]
        y += moves[d][1]
        dist += 1
    return {'position': [x, y], 'distance': dist - 1}


@test.describe('Basic mazes')
def example_tests():

    def rot(dir):
        if dir[0] > 0:
            return [0, 1]
        if dir[1] > 0:
            return [-1, 0]
        if dir[0] < 0:
            return [0, -1]
        if dir[1] < 0:
            return [1, 0]
        return dir

    def circle_maze(radius):
        w = radius*2+2
        h = w
        board = [[' ' for i in range(w)] for j in range(h)]
        for x in range(w):
            board[0][x] = '#'
            board[h - 1][x] = '#'
        for y in range(h):
            board[y][0] = '#'
            board[y][w-1] = '#'

        board[radius][0] = '*'
        pos = [radius, radius]
        dir = [1, 0]
        step = 1
        for i in range(4*radius-1):
            pos[0] += dir[0]*step
            pos[1] += dir[1]*step
            dir = rot(dir)
            if i and i % 2 == 0:
                step += 1
            board[pos[1]][pos[0]] = '/' if i % 2 else '\\'
        return list(map(lambda v: ''.join(v), board))

    @test.it('Tests the maze provided in the description')
    def example_test_case():
        board = [
            "##############",
            "#        \\   #",
            "*   \\        #",
            "#            #",
            "#   \\    /   #",
            "##############"]
        res = {
            'position': [0, 1],
            'distance': 22
        }
        test.assert_equals(exit_from_maze(board), res)

    @test.it('Tests several mazes')
    def several_mazes_case():
        board = [
            "###*###",
            "#/ /  #",
            "#     #",
            "#     #",
            "#\\    #",
            "#######"
        ]
        res = {'position': [6, 4], 'distance': 10}
        test.assert_equals(exit_from_maze(board), res)

    @test.it('Tests circular maze')
    def circular_maze_case():
        for i in range(2, 15):
            board = circle_maze(i)
            expected = _exit_from_maze(board)
            actual = exit_from_maze(board)
            test.assert_equals(actual, expected)


@test.describe('Random Tests')
def random_tests():

    # The reference solution should be placed here, in order to prevent the warrior from abusing your reference solution
    def random_maze():
        w = floor(random() * 50) + 3
        h = floor(random() * 50) + 3
        board = [[' ' for i in range(w)] for j in range(h)]
        for x in range(w):
            board[0][x] = '#'
            board[h - 1][x] = '#'
        for y in range(h):
            board[y][0] = '#'
            board[y][w-1] = '#'
        starting_side = floor(random() * 4)
        temp = h - 2 if starting_side % 2 else w-2
        starting_pos = floor(random() * temp + 1)
        if starting_side == 0:
            board[0][starting_pos] = '*'
        elif starting_side == 1:
            board[starting_pos][0] = "*"
        elif starting_side == 2:
            board[h - 1][starting_pos] = "*"
        else:
            board[starting_pos][w - 1] = "*"
        for i in range(floor(random() * w * h * 2)):
            board[floor(random() * (h - 2))+1][floor(random() *
                                                     (w - 2))+1] = '/' if random() < 0.5 else '\\'
        board = list(map(lambda v: ''.join(v), board))
        return board

    # The number of random tests must be enough to test every possible aspects of the input.
    # The rule of thumb is 100 tests, but you have to think carefully according to the requirements of your Kata.
    @test.it('Random Test Cases')
    def random_test_cases():
        for i in range(85):
            board = random_maze()
            expected = _exit_from_maze(board)
            actual = exit_from_maze(board)
            test.assert_equals(actual, expected)
